#include <iostream>
#include <string>
#include <cstring>
#include <functional>

#define SHA1_RESULT_BIT 160
#define SHA1_RESULT_UINT8 (SHA1_RESULT_BIT / 8)         // 20
#define SHA1_RESULT_UINT32 (SHA1_RESULT_UINT8 / 4)      // 5

#define SHA1_BLOB_BIT 512
#define SHA1_BLOB_UINT8 (SHA1_BLOB_BIT / 8)             // 64
#define SHA1_BLOB_UINT32 (SHA1_BLOB_UINT8 / 4)          // 16

#define SHA1_REST_BIT 448
#define SHA1_REST_UINT8 (SHA1_REST_BIT / 8)             // 56
#define SHA1_REST_UINT32 (SHA1_REST_UINT8 / 4)          // 14

#define SHA1_OPERATION_CNT 80
#define SHA1_ROUND_CNT 4
#define SHA1_ROUND_LEN (SHA1_OPERATION_CNT / SHA1_ROUND_CNT) // 20

class SHA1Digest
{
public:
	SHA1Digest() : _byteCount(0)
	{
		Reset();
	}
	SHA1Digest(const void* input, size_t length) : SHA1Digest() {}

	void Update(const void* input, size_t length)
	{
		const unsigned char* buff = reinterpret_cast<const unsigned char*>(input);

		if (_countLow + (length << 3) < _countLow)
		{
			++_countHight;
		}
		_countLow += length << 3;
		_countHight += length >> 29;

		while (length--)
		{
			reinterpret_cast<unsigned char*>(&_data[0])[_byteCount++] = *buff++;
			if (_byteCount == SHA1_BLOB_UINT8)
			{
				// TODO : 1. group 512-bits/64-bytes
				BytesReverse(_data, SHA1_BLOB_UINT32);
				Transform();
				_byteCount = 0;
			}
		}
	}

	const unsigned char* DigestText()
	{
		Final();
		return _digest;
	}
	std::string GetString(bool isUpcase = true)
	{
		const unsigned char* pstr = DigestText();
		size_t length = GetSize();

		// TODO : 10. get 40-chars string
		return BytesToHex(pstr, length, isUpcase);
	}
	size_t GetSize()
	{
		return SHA1_RESULT_UINT8;
	}
	void Reset()
	{
		InitH();
		_countLow = _countHight = 0;
		_byteCount = 0;
		std::memset(_data, 0, sizeof(_data));
		std::memset(_digest, 0, sizeof(_digest));
	}
private:
	SHA1Digest(const SHA1Digest&) = delete;
	SHA1Digest& operator = (const SHA1Digest&) = delete;


	void Transform()
	{
		uint32_t alpha[SHA1_RESULT_UINT32];
		uint32_t W[SHA1_OPERATION_CNT];

		// TODO : 2. fill W[] (0 ~ 15)
		for (int i = 0; i < SHA1_BLOB_UINT32; W[i] = _data[i], ++i) {}

		// TODO : 3. fill W[] (16 ~ 79)
		for (int i = SHA1_BLOB_UINT32; i < SHA1_OPERATION_CNT; W[i] = Expand(W, i), ++i) {}

		// TODO : 4. fill A, B, C, D, E
		for (int i = 0; i < SHA1_RESULT_UINT32; alpha[i] = _h[i], ++i) {}

		// TODO : 5. operator round 80
		for (int i = 0; i < SHA1_OPERATION_CNT; Round(alpha, W, i++)) {}

		// TODO : 6. update H[]
		for (int i = 0; i < SHA1_RESULT_UINT32; _h[i] += alpha[i], ++i) {}
	}
	void Final()
	{
		static unsigned int bitsOfByte = 8;

		// TODO : 7. cover bit (1)
		reinterpret_cast<unsigned char*>(&_data[0])[_byteCount++] = 0x80;

		// TODO : 7. cover bit (000...000)
		std::memset(reinterpret_cast<unsigned char*>(&_data[0]) + _byteCount, 0, SHA1_BLOB_UINT8 - _byteCount);
		if (_byteCount > SHA1_REST_UINT8)
		{
			BytesReverse(_data, SHA1_BLOB_UINT32);
			Transform();
			std::memset(_data, 0, sizeof(_data));
		}

		BytesReverse(_data, SHA1_BLOB_UINT32);

		// TODO : 8. add bits count
		_data[14] = _countHight;
		_data[15] = _countLow;

		Transform();

		// TODO : 9. get bytes array from words array
		for (int i = 0; i < SHA1_RESULT_UINT8; ++i)
		{
			_digest[i] = _h[i >> 2] >> (bitsOfByte * (3 - (i & 0x03))) & 0xFF;
		}
	}
	void InitH()
	{
		for (int i = 0; i < SHA1_RESULT_UINT32; _h[i] = H_INIT[i], ++i) {}
	}

private:
	uint32_t _h[SHA1_RESULT_UINT32];
	uint32_t _data[SHA1_BLOB_UINT32];
	uint32_t _countLow;
	uint32_t _countHight;
	uint32_t _byteCount;
	unsigned char _digest[SHA1_RESULT_UINT8];

private:
	static uint32_t H_INIT[SHA1_RESULT_UINT32];
	static uint32_t K[SHA1_ROUND_CNT];
	static std::function<uint32_t(uint32_t, uint32_t, uint32_t)> F[SHA1_ROUND_CNT];
	static uint32_t S(uint32_t x, int n);
	static uint32_t Expand(uint32_t W[], int i);
	static void Round(uint32_t alpha[], uint32_t W[], int i);

public:
	static std::string BytesToHex(const unsigned char* input, size_t length, bool isUpcase);
	static void BytesReverse(uint32_t data[], size_t length);
};
